import {numberToWeekHans} from "./describers/week";
import {Normalized} from "./types";
import moment = require("moment");

const matchTime = (mm: moment.Moment, n: Normalized): boolean => {
  const minute = mm.minute()
  const hour = mm.hour()

  return n.minutes.indexOf(minute) >= 0 && n.hours.indexOf(hour) >= 0
}

const matchDate = (mm: moment.Moment, n: Normalized): boolean => {
  const date = mm.date()
  const month = mm.month() + 1
  const week = mm.weekday()

  if (n.daysAnded) {
    return n.dates.indexOf(date) >= 0 &&
      n.months.indexOf(month) >= 0 &&
      n.weekdays.indexOf(week) >= 0
  } else {
    return n.months.indexOf(month) >= 0 &&
      (n.dates.indexOf(date) >= 0 || n.weekdays.indexOf(week) >= 0)
  }
}

const match = (mm: moment.Moment, n: Normalized): boolean => {
  return matchTime(mm, n) && matchDate(mm, n)
}


const MINUTES_OF_YEAR = 366 * 24 * 60


export class Iterator {


  private dayIndex = 0
  private monthIndex = 0
  private minuteIndex = 0
  private hourIndex = 0
  private year = 0
  private readonly mm: moment.Moment;


  private isMatch() {
    return match(this.mm, this.normalized)
  }

  private isMatchTime(): boolean {
    return matchTime(this.mm, this.normalized)
  }

  private isMatchDate(): boolean {
    return matchDate(this.mm, this.normalized)
  }

  private moveToTimeAlign(hourStart: number, minuteStart: number) {
    for (let h = hourStart; h <= 23; h++) {
      this.mm.hour(h)
      for (let m = h == hourStart ? minuteStart : 0; m <= 59; m++) {
        this.mm.minute(m)
        if (this.isMatchTime()) {
          return;
        }
      }
    }

    this.stepDay()
    this.moveToDateAlign()
    this.moveToTimeAlign(0, 0)
  }


  private moveToDateAlign() {
    while (!this.isMatchDate()) {
      this.stepDay();
    }

    this.mm.hour(0).minute(0)
  }

  private stepDay() {
    this.mm.add(1, 'day')
  }

  private step() {
    this.mm.add(1, 'minute')
  }

  constructor(readonly normalized: Normalized, from: Date = new Date()) {
    this.mm = moment(from)
    this.year = this.mm.year()
  }

  next() {
    if (!this.isMatchDate()) {
      this.moveToDateAlign()
    }

    if (!this.isMatchTime()) {
      this.moveToTimeAlign(this.mm.hour(), this.mm.minute())
    }

    const date = this.mm.toDate()
    this.mm.add(1, "minute")
    return date
  }
}


export function nextDate(normalizeCrontab: Normalized, fromDate: Date): Date {

  const iter = new Iterator(normalizeCrontab, fromDate)

  return iter.next()

}

const formatString = 'YYYY-MM-DD HH:mm'

export function format(d: Date, withWeek: boolean = false) {
  const dateString = moment(d).format(formatString)
  return withWeek ? `${dateString} (${numberToWeekHans(d.getDay())})` : dateString
}
